﻿using GammaJul.ReSharper.GammaXaml.Highlightings.CSharp;
using GammaJul.ReSharper.GammaXaml.Psi.Xaml;
using JetBrains.Annotations;
using JetBrains.ReSharper.Daemon;
using JetBrains.ReSharper.Daemon.CSharp.Stages;
using JetBrains.ReSharper.Daemon.Stages;
using JetBrains.ReSharper.Daemon.Stages.Dispatcher;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Xaml.Impl.Resolve;

namespace GammaJul.ReSharper.GammaXaml.Analysis.CSharp {

	/// <summary>
	/// Analyzer that checks that :
	///  - A dependency property field ends with Property
	///  - A dependency property key field ends with PropertyKey
	///  - A dependency property/property key field is static readonly
	///  - A corresponding CLR property for a dependency property has a matching type
	/// </summary>
	[ElementProblemAnalyzer(
		new[] { typeof(IFieldDeclaration) },
		HighlightingTypes = new[] {
			typeof(DpFieldMustBeStaticReadOnlyHighlighting),
			typeof(MismatchedDpAccessorTypeHighlighting),
			typeof(DpFieldMustEndWithPropertyHighlighting),
			typeof(DpKeyFieldShouldEndWithPropertyKeyHighlighting)
		}
	)]
	public class DpFieldProblemAnalyzer : ElementProblemAnalyzer<IFieldDeclaration> {

		protected override void Run(IFieldDeclaration element, ElementProblemAnalyzerData data, IHighlightingConsumer consumer) {
			
			// we are considering only properties that have an initializer
			// this way, we are allowing "normal" fields to be defined to store dependency properties
			// without triggering false positive everywhere
			var expressionInitializer = element.Initial as IExpressionInitializer;
			if (expressionInitializer == null || expressionInitializer.Value.IsConstantValue())
				return;

			// checks that dependency property fields have the correct suffix
			IXamlTypeCache xamlTypeCache = XamlTypeCache.GetCache(element);
			if (Equals(element.Type, xamlTypeCache.DependencyProperty))
				CheckDpFieldName(element, consumer);
			else if (Equals(element.Type, xamlTypeCache.DependencyPropertyKey()))
				CheckDpKeyFieldName(element, consumer);
			else
				return;

			CheckAccessors(element, xamlTypeCache, consumer);

			// adds a warning if the field isn't static or readonly
			if (!element.IsStatic || !element.IsReadonly)
				consumer.AddHighlighting(new DpFieldMustBeStaticReadOnlyHighlighting(element));
		}

		/// <summary>
		/// Checks that a dependency property field declared type has correct accessor types.
		/// </summary>
		/// <param name="fieldDeclaration">The dependency property field declaration.</param>
		/// <param name="xamlTypeCache">A <see cref="IXamlTypeCache"/> instance.</param>
		/// <param name="consumer">The highlighting consumer.</param>
		private static void CheckAccessors([NotNull] IFieldDeclaration fieldDeclaration, [NotNull] IXamlTypeCache xamlTypeCache, [NotNull] IHighlightingConsumer consumer) {
			DpInitInfo dpInitInfo = fieldDeclaration.GetDpOrDpKeyInitInfo(xamlTypeCache);
			ITypeofExpression typeofExpr = dpInitInfo.PropertyTypeofExpression;
			if (typeofExpr == null || dpInitInfo.RegistrationMethod == DpRegistrationMethod.Unknown)
				return;

			IType type = typeofExpr.ArgumentType;
			if (dpInitInfo.RegistrationMethod.IsAttached()) {

				// check attached getter return types
				foreach (IMethod getter in fieldDeclaration.DeclaredElement.GetDpAttachedGetters(xamlTypeCache)) {
					if (!Equals(getter.ReturnType, type)) {
						consumer.AddHighlighting(
							new MismatchedDpAccessorTypeHighlighting(fieldDeclaration.DeclaredElement, type, getter),
							typeofExpr.TypeName.GetHighlightingRange());
					}
				}

				// check attached getter second parameter type
				foreach (IMethod setter in fieldDeclaration.DeclaredElement.GetDpAttachedSetters(xamlTypeCache)) {
					if (!Equals(setter.Parameters[1].Type, type)) {
						consumer.AddHighlighting(
							new MismatchedDpAccessorTypeHighlighting(fieldDeclaration.DeclaredElement, type, setter),
							typeofExpr.TypeName.GetHighlightingRange());
					}
				}

			}

			else {

				// check property
				IProperty property = fieldDeclaration.DeclaredElement.GetDpClrProperty(xamlTypeCache);
				if (property != null && !Equals(property.Type, typeofExpr.ArgumentType)) {
					consumer.AddHighlighting(
						new MismatchedDpAccessorTypeHighlighting(fieldDeclaration.DeclaredElement, type, property),
						typeofExpr.TypeName.GetHighlightingRange());
				}

			}
		}

		/// <summary>
		/// Checks that a dependency property field name ends with Property.
		/// </summary>
		/// <param name="fieldDeclaration">The dependency property field declaration.</param>
		/// <param name="consumer">The highlighting consumer.</param>
		private static void CheckDpFieldName([NotNull] IFieldDeclaration fieldDeclaration, [NotNull] IHighlightingConsumer consumer) {
			if (fieldDeclaration.DeclaredName.IsDpFieldName())
				return;

			IPsiSourceFile psiSourceFile = fieldDeclaration.GetSourceFile();
			if (psiSourceFile == null)
				return;

			consumer.AddHighlighting(new DpFieldMustEndWithPropertyHighlighting(fieldDeclaration, psiSourceFile));
		}

		/// <summary>
		/// Checks that a dependency property key field name ends with PropertyKey.
		/// </summary>
		/// <param name="fieldDeclaration">The dependency property key field declaration.</param>
		/// <param name="consumer">The highlighting consumer.</param>
		private static void CheckDpKeyFieldName([NotNull] IFieldDeclaration fieldDeclaration, [NotNull] IHighlightingConsumer consumer) {
			if (fieldDeclaration.DeclaredName.IsDpKeyFieldName())
				return;

			IPsiSourceFile psiSourceFile = fieldDeclaration.GetSourceFile();
			if (psiSourceFile == null)
				return;

			consumer.AddHighlighting(new DpKeyFieldShouldEndWithPropertyKeyHighlighting(fieldDeclaration, psiSourceFile));
		}

	}

}